package util

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

var httpClient = &http.Client{Timeout: 5 * time.Second}

func HttpGet(url string, header http.Header, result interface{}) error {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return err
	}

	if header != nil {
		req.Header = header
	}

	resp, err := httpClient.Do(req)
	if err != nil {
		return err
	}

	return responseAndValidate(resp, result)
}

func HttpPostForm(url string, values url.Values, result interface{}) error {
	resp, err := httpClient.PostForm(url, values)
	if err != nil {
		return err
	}

	return responseAndValidate(resp, result)
}

func HttpPostFormHeader(url string, header http.Header, values url.Values, result interface{}) error {
	if header == nil {
		header = http.Header{}
	}
	header.Add("Content-Type", "application/x-www-form-urlencoded")
	return HttpPost(url, header, strings.NewReader(values.Encode()), result)
}

func HttpPostJson(url string, value interface{}, result interface{}) error {
	return HttpPostJsonHeader(url, nil, value, result)
}

func HttpPostJsonHeader(url string, header http.Header, value interface{}, result interface{}) error {
	if header == nil {
		header = http.Header{}
	}
	header.Add("Content-Type", "application/json")

	if value, ok := value.(string); ok {
		return HttpPost(url, header, bytes.NewReader([]byte(value)), result)
	}

	valueByte, err := json.Marshal(value)
	if err != nil {
		return err
	}
	return HttpPost(url, header, bytes.NewReader(valueByte), result)
}

func HttpPost(url string, header http.Header, body io.Reader, result interface{}) error {
	request, err := http.NewRequest("POST", url, body)
	if err != nil {
		return err
	}

	if header != nil {
		request.Header = header
	}

	resp, err := httpClient.Do(request)
	if err != nil {
		return err
	}

	return responseAndValidate(resp, result)
}

type HttpStatusError struct {
	HttpStatus int
	Message    string
}

func (hse HttpStatusError) Error() string {
	return fmt.Sprintf("request the interface to return an unsuccessful status %d", hse.HttpStatus)
}

func responseAndValidate(resp *http.Response, result interface{}) (err error) {
	if !(resp.StatusCode >= http.StatusOK &&
		resp.StatusCode < http.StatusMultipleChoices) {
		return HttpStatusError{resp.StatusCode, ""}
	}

	defer resp.Body.Close()
	var buf []byte
	buf, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	if result, ok := result.(*[]byte); ok {
		*result = buf
		return
	}

	err = json.Unmarshal(buf, result)
	if err != nil {
		return
	}

	return
}
